/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.nanosn.coffeemaker.server.restlet.gae;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.restlet.Context;
import org.restlet.data.Encoding;
import org.restlet.data.MediaType;
import org.restlet.data.Preference;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.CharacterSet;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ResourceException;

/**
 *
 * @author selkhateeb
 */
public class GwtRpcRestletUtils {

	private static final String ACCEPT_ENCODING = "Accept-Encoding";
	private static final String CONTENT_ENCODING_GZIP = "gzip";
	private static final String EXPECTED_CONTENT_TYPE = "text/x-gwt-rpc";
	private static final String EXPECTED_CHARSET = "charset=utf-8";
	private static final String CHARSET_UTF8 = "utf-8";//"UTF-8";
	private static final String GENERIC_FAILURE_MSG =
		"The call failed on the server; see server log for details";

	/**
	 * Returns the content of an {@link HttpServletRequest} by decoding it using
	 * the UTF-8 charset.
	 *
	 * @param request the servlet request whose content we want to read
	 * @param checkHeaders Specify 'true' to check the Content-Type header to see
	 *          that it matches the expected value 'text/x-gwt-rpc' and the
	 *          content encoding is UTF-8. Disabling this check may allow some
	 *          types of cross type security attacks.
	 * @return the content of an {@link HttpServletRequest} by decoding it using
	 *         the UTF-8 charset
	 * @throws IOException if the requests input stream cannot be accessed, read
	 *           from or closed
	 * @throws ServletException if the content length of the request is not
	 *           specified of if the request's content type is not
	 *           'text/x-gwt-rpc' and 'charset=utf-8'
	 */
	public static String readContentAsUtf8(Request request,
					       boolean checkHeaders) throws
		ResourceException, IOException {
		int contentLength = (int) request.getEntity().getAvailableSize();//ContentLength();
		if (contentLength == -1) {
			// Content length must be known.
			throw new ResourceException(
				Status.CLIENT_ERROR_BAD_REQUEST,
				"Content-Length must be specified");
		}

		if (checkHeaders) {
			checkContentType(request);
			checkCharacterEncoding(request);
		}

		InputStream in = request.getEntity().getStream();
		try {
			byte[] payload = new byte[contentLength];
			int offset = 0;
			int len = contentLength;
			int byteCount;
			while (offset < contentLength) {
				byteCount = in.read(payload, offset, len);
				if (byteCount == -1) {
					throw new ResourceException(
						Status.CLIENT_ERROR_LENGTH_REQUIRED,
						"Client did not send " +
						contentLength +
						" bytes as expected");
				}
				offset += byteCount;
				len -= byteCount;
			}
			return new String(payload, CHARSET_UTF8);
		}
		finally {
			if (in != null) {
				in.close();
			}
		}
	}

	/**
	 * Performs Content-Type validation of the incoming request.
	 *
	 * @param request the incoming request.
	 * @throws ServletException if the request's content type is not
	 *           'text/x-gwt-rpc'
	 */
	private static void checkContentType(Request request) throws
		ResourceException {
		String contentType =
			request.getEntity().getMediaType().getName();
		boolean contentTypeIsOkay = false;

		if (contentType != null) {
			contentType = contentType.toLowerCase();
			/*
			 * The Content-Type must be text/x-gwt-rpc.
			 *
			 * NOTE:We use startsWith because some servlet engines, i.e. Tomcat, do
			 * not remove the charset component but others do.
			 */
			if (contentType.startsWith(EXPECTED_CONTENT_TYPE)) {
				contentTypeIsOkay = true;
			}
		}

		if (!contentTypeIsOkay) {
			throw new ResourceException(
				Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE,
				"Content-Type was '" +
				(contentType == null ? "(null)"
				 : contentType) +
				"'. Expected '" +
				EXPECTED_CONTENT_TYPE + "'.");
		}
	}

	/**
	 * Performs validation of the character encoding.
	 *
	 * @param request the incoming request.
	 * @throws ServletException if requests encoding is not UTF-8
	 */
	private static void checkCharacterEncoding(Request request) throws
		ResourceException {
		boolean encodingOkay = false;
		//request.getEntity().getCharacterSet().UTF_8
		CharacterSet characterEncoding = request.getEntity().
			getCharacterSet();
		if (characterEncoding != null) {
			if (characterEncoding.equals(CharacterSet.UTF_8)) {
				encodingOkay = true;
			}
		}

		if (!encodingOkay) {
			throw new ResourceException(
				Status.CLIENT_ERROR_NOT_ACCEPTABLE,
				"Character Encoding is '" +
				(characterEncoding == null ? "(null)"
				 : characterEncoding) +
				"'.  Expected '" +
				EXPECTED_CHARSET + "'");
		}
	}

	/**
	 * Called when the servlet itself has a problem, rather than the invoked
	 * third-party method. It writes a simple 500 message back to the client.
	 *
	 * @param servletContext
	 * @param response
	 * @param failure
	 */
	public static void writeResponseForUnexpectedFailure(
		Context context, Response response,
		Throwable failure) {
		context.getLogger().log(Level.SEVERE,
					"Exception while dispatching incoming RPC call",
					failure);

		// Send GENERIC_FAILURE_MSG with 500 status.
		//
		Representation errorRepresentation = new StringRepresentation(
			GENERIC_FAILURE_MSG, MediaType.TEXT_PLAIN);
		response.setStatus(Status.SERVER_ERROR_INTERNAL);
		response.setEntity(errorRepresentation);
	}

	/**
	 * Returns <code>true</code> if the {@link HttpServletRequest} accepts Gzip
	 * encoding. This is done by checking that the accept-encoding header
	 * specifies gzip as a supported encoding.
	 *
	 * @param request the request instance to test for gzip encoding acceptance
	 * @return <code>true</code> if the {@link HttpServletRequest} accepts Gzip
	 *         encoding
	 */
	public static boolean acceptsGzipEncoding(Request request) {
		assert (request != null);
		return request.getClientInfo().getAcceptedEncodings().contains(
			new Preference<Encoding>(Encoding.GZIP));
//		String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
//		if (null == acceptEncoding) {
//			return false;
//		}

//		return (acceptEncoding.indexOf(CONTENT_ENCODING_GZIP) != -1);
	}

	public static Representation writeResponse(Context context,
					 Response response,
					 String responseContent,
					 boolean gzipResponse)
		throws IOException {

		byte[] responseBytes = responseContent.getBytes(CHARSET_UTF8);
		if (gzipResponse) {
			// Compress the reply and adjust headers.
			//
			ByteArrayOutputStream output = null;
			GZIPOutputStream gzipOutputStream = null;
			Throwable caught = null;
			try {
				output = new ByteArrayOutputStream(
					responseBytes.length);
				gzipOutputStream = new GZIPOutputStream(output);
				gzipOutputStream.write(responseBytes);
				gzipOutputStream.finish();
				gzipOutputStream.flush();
				responseBytes = output.toByteArray();
			}
			catch (IOException e) {
				caught = e;
			}
			finally {
				if (null != gzipOutputStream) {
					gzipOutputStream.close();
				}
				if (null != output) {
					output.close();
				}
			}

			if (caught != null) {
				context.getLogger().log(Level.SEVERE,
							"Unable to compress response",
							caught);
//				response.sendError(
//					HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
				return null;
			}
		}

		return new GwtRpcRepresentation(responseBytes);

		// Send the reply.
		//
//		response.setEntity(new GwtRpcRepresentation(responseBytes));
//		response.setStatus(Status.SUCCESS_OK);
	}
}
